﻿using System.Diagnostics.Contracts;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.IO;

namespace SS.Common.ImageResizer
{
    using System;

    public class ImageResizer : IDisposable
    {
        private byte[] _imageBytes;
        private readonly BitmapImage _orgBitMap;

        public ImageResizer(string path)
        {
            this._imageBytes = LoadImageData(path);
            this._orgBitMap = this.LoadBitmapImage(this._imageBytes);
        }

        public ImageResizer(byte[] imageBytes)
        {
            this._imageBytes = imageBytes;
            this._orgBitMap = this.LoadBitmapImage(this._imageBytes);
        }

        public byte[] Resize(int width, ImageEncoding encoding)
        {
            return this.Resize(width, 0, encoding);
        }

        public byte[] Resize(int width, int height, ImageEncoding encoding)
        {
            return this.Resize(width, height, true, encoding);
        }

        public byte[] Resize(int width, int height, bool crop, ImageEncoding encoding)
        {
            if (width < 0)
            {
                throw new ArgumentException("width < 0");
            }
            if (height < 0)
            {
                throw new ArgumentException("height < 0");
            }

            if (width > this._orgBitMap.PixelWidth)
            {
                width = this._orgBitMap.PixelWidth;
            }
            if (height > this._orgBitMap.PixelHeight)
            {
                height = this._orgBitMap.PixelHeight;
            }

            var bitmapSource = (BitmapSource)null;
            if (width > 0 && height > 0 && crop)
            {
                bitmapSource = this.ScaleToFill(width, height);
            }
            else if (width > 0 && height > 0 && !crop)
            {
                bitmapSource = this.ScaleToFit(width, height);
            }
            else if (width > 0)
            {
                bitmapSource = this.ResizeImageByWidth(this._imageBytes, width);
            }
            this._imageBytes = this.EncodeImageData(bitmapSource, encoding);
            return this._imageBytes;
        }

        private BitmapSource ScaleToFill(int width, int height)
        {
            Contract.Requires(width > 0);
            Contract.Requires(height > 0);

            BitmapSource source;
            ImageSize imageSize;

            var heightRatio = height / (double)_orgBitMap.PixelHeight;
            var widthRatio = width / (double)_orgBitMap.PixelWidth;

            if (heightRatio > widthRatio)
            {
                source = this.ResizeImageByHeight(this._imageBytes, height);
                imageSize = new ImageSizeCalculator(source.PixelWidth, height).ScaleToFill(width, height);
            }
            else
            {
                source = this.ResizeImageByWidth(this._imageBytes, width);
                imageSize = new ImageSizeCalculator(width, source.PixelHeight).ScaleToFill(width, height);
            }
            return new CroppedBitmap(source, new Int32Rect(imageSize.XOffset, imageSize.YOffset, imageSize.Width, imageSize.Height));
        }

        private BitmapSource ScaleToFit(int width, int height)
        {
            Contract.Requires(width > 0);
            Contract.Requires(height > 0);

            double heightRatio = _orgBitMap.PixelHeight / height;
            double widthRatio = _orgBitMap.PixelWidth / width;

            return heightRatio > widthRatio
                       ? this.ResizeImageByHeight(this._imageBytes, height)
                       : this.ResizeImageByWidth(this._imageBytes, width);
        }

        public void SaveToFile(string path)
        {
            this.SaveImageToFile(this._imageBytes, path);
        }

        public void Dispose()
        {
            this._imageBytes = null;
        }

        private BitmapSource ResizeImageByWidth(byte[] imageData, int width)
        {
            var bitmapImage = new BitmapImage();
            bitmapImage.BeginInit();
            bitmapImage.DecodePixelWidth = width;
            bitmapImage.StreamSource = new MemoryStream(imageData);
            bitmapImage.CreateOptions = BitmapCreateOptions.IgnoreColorProfile;
            bitmapImage.CacheOption = BitmapCacheOption.Default;
            bitmapImage.EndInit();
            return bitmapImage;
        }

        private BitmapSource ResizeImageByHeight(byte[] imageData, int height)
        {
            var bitmapImage = new BitmapImage();
            bitmapImage.BeginInit();
            bitmapImage.DecodePixelHeight = height;
            bitmapImage.StreamSource = new MemoryStream(imageData);
            bitmapImage.CreateOptions = BitmapCreateOptions.IgnoreColorProfile;
            bitmapImage.CacheOption = BitmapCacheOption.Default;
            bitmapImage.EndInit();
            return bitmapImage;
        }

        private byte[] EncodeImageData(ImageSource image, ImageEncoding encoding)
        {
            byte[] buffer = null;
            var bitmapEncoder = (BitmapEncoder)null;
            switch (encoding)
            {
                case ImageEncoding.Jpg100:
                    bitmapEncoder = new JpegBitmapEncoder
                                        {
                                            QualityLevel = 100
                                        };
                    break;
                case ImageEncoding.Jpg95:
                    bitmapEncoder = new JpegBitmapEncoder
                                        {
                                            QualityLevel = 95
                                        };
                    break;
                case ImageEncoding.Jpg90:
                    bitmapEncoder = new JpegBitmapEncoder
                                        {
                                            QualityLevel = 90
                                        };
                    break;
                case ImageEncoding.Jpg:
                    bitmapEncoder = new JpegBitmapEncoder();
                    break;
                case ImageEncoding.Gif:
                    bitmapEncoder = new GifBitmapEncoder();
                    break;
                case ImageEncoding.Png:
                    bitmapEncoder = new PngBitmapEncoder();
                    break;
                case ImageEncoding.Tiff:
                    bitmapEncoder = new TiffBitmapEncoder();
                    break;
                case ImageEncoding.Bmp:
                    bitmapEncoder = new BmpBitmapEncoder();
                    break;
                case ImageEncoding.Wmp:
                    bitmapEncoder = new WmpBitmapEncoder();
                    break;
            }
            if (image is BitmapSource)
            {
                using (var memoryStream = new MemoryStream())
                {
                    if (bitmapEncoder != null)
                    {
                        var bitmapFrame = BitmapFrame.Create(image as BitmapSource);
                        bitmapEncoder.Frames.Add(bitmapFrame);
                        bitmapEncoder.Save(memoryStream);
                    }
                    memoryStream.Seek(0L, SeekOrigin.Begin);
                    buffer = new byte[memoryStream.Length];
                    using (var binaryReader = new BinaryReader(memoryStream))
                    {
                        binaryReader.Read(buffer, 0, (int)memoryStream.Length);
                    }
                }
            }
            return buffer;
        }

        private static byte[] LoadImageData(string filePath)
        {
            byte[] numArray = null;
            try
            {
                using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
                using (var binaryReader = new BinaryReader(fileStream))
                {
                    numArray = binaryReader.ReadBytes((int)fileStream.Length);
                }
            }
            catch
            {
            }
            return numArray;
        }

        private BitmapImage LoadBitmapImage(byte[] bytes)
        {
            var bitmapImage = new BitmapImage();
            bitmapImage.BeginInit();
            bitmapImage.StreamSource = new MemoryStream(bytes);
            bitmapImage.CreateOptions = BitmapCreateOptions.IgnoreColorProfile;
            bitmapImage.CacheOption = BitmapCacheOption.Default;
            bitmapImage.EndInit();
            return bitmapImage;
        }

        private void SaveImageToFile(byte[] bytes, string path)
        {
            using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write))
            using (var binaryWriter = new BinaryWriter(fileStream))
            {
                binaryWriter.Write(bytes);
            }
        }
    }
}
